import ctypes
import numpy as np
import OpenGL.GL as gl
from OpenGL.GL.shaders import compileProgram, compileShader

MAX_BONE_INFLUENCE = 4

class Vertex:
    def __init__(self, position, normal, texCoords, tangent, bitangent, boneIDs, weights):
        self.position = position
        self.normal = normal
        self.texCoords = texCoords
        self.tangent = tangent
        self.bitangent = bitangent
        self.boneIDs = boneIDs
        self.weights = weights

class Texture:
    def __init__(self, id, type, path):
        self.id = id
        self.type = type
        self.path = path

class Mesh:
    def __init__(self, vertices, indices, textures):
        self.vertices = vertices
        self.indices = indices
        self.textures = textures
        self.VAO = gl.glGenVertexArrays(1)
        self.setup_mesh()

    def setup_mesh(self):
        self.VBO = gl.glGenBuffers(1)
        self.EBO = gl.glGenBuffers(1)
        
        gl.glBindVertexArray(self.VAO)

        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.VBO)
        vertex_data = np.array([vertex_data for vertex in self.vertices for vertex_data in (
            vertex.position.tolist() + vertex.normal.tolist() + vertex.texCoords.tolist() +
            vertex.tangent.tolist() + vertex.bitangent.tolist() + vertex.boneIDs + vertex.weights)], dtype=np.float32)
        gl.glBufferData(gl.GL_ARRAY_BUFFER, vertex_data.nbytes, vertex_data, gl.GL_STATIC_DRAW)

        gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.EBO)
        index_data = np.array(self.indices, dtype=np.uint32)
        gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, index_data.nbytes, index_data, gl.GL_STATIC_DRAW)

        stride = vertex_data.itemsize * 18  # 3 + 3 + 2 + 3 + 3 + 4 + 4

        # vertex positions
        gl.glEnableVertexAttribArray(0)
        gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, stride, ctypes.c_void_p(0))
        # vertex normals
        gl.glEnableVertexAttribArray(1)
        gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, stride, ctypes.c_void_p(12))
        # vertex texture coords
        gl.glEnableVertexAttribArray(2)
        gl.glVertexAttribPointer(2, 2, gl.GL_FLOAT, gl.GL_FALSE, stride, ctypes.c_void_p(24))
        # vertex tangent
        gl.glEnableVertexAttribArray(3)
        gl.glVertexAttribPointer(3, 3, gl.GL_FLOAT, gl.GL_FALSE, stride, ctypes.c_void_p(32))
        # vertex bitangent
        gl.glEnableVertexAttribArray(4)
        gl.glVertexAttribPointer(4, 3, gl.GL_FLOAT, gl.GL_FALSE, stride, ctypes.c_void_p(44))
        # bone ids
        gl.glEnableVertexAttribArray(5)
        gl.glVertexAttribIPointer(5, 4, gl.GL_INT, stride, ctypes.c_void_p(56))
        # weights
        gl.glEnableVertexAttribArray(6)
        gl.glVertexAttribPointer(6, 4, gl.GL_FLOAT, gl.GL_FALSE, stride, ctypes.c_void_p(72))

        gl.glBindVertexArray(0)

    def draw(self, shader):
        diffuseNr = 1
        specularNr = 1
        normalNr = 1
        heightNr = 1

        for i, texture in enumerate(self.textures):
            gl.glActiveTexture(gl.GL_TEXTURE0 + i)
            name = texture.type
            number = ''

            if name == 'texture_diffuse':
                number = str(diffuseNr)
                diffuseNr += 1
            elif name == 'texture_specular':
                number = str(specularNr)
                specularNr += 1
            elif name == 'texture_normal':
                number = str(normalNr)
                normalNr += 1
            elif name == 'texture_height':
                number = str(heightNr)
                heightNr += 1

            gl.glUniform1i(gl.glGetUniformLocation(shader.program, (name + number).encode('utf-8')), i)
            gl.glBindTexture(gl.GL_TEXTURE_2D, texture.id)

        gl.glBindVertexArray(self.VAO)
        gl.glDrawElements(gl.GL_TRIANGLES, len(self.indices), gl.GL_UNSIGNED_INT, None)
        gl.glBindVertexArray(0)
        gl.glActiveTexture(gl.GL_TEXTURE0)
